#include <memory.h>
#include "fileenc.h"
#include "hfile.h"
#include "htea.h"
#include "hcrc32.h"

#define GDSB_ENC_MAXKEYCOUNT            16
#define GDSB_ENC_SIGNATURE              0x67647362      // encrypt header signature 'gdsb'
#define GDSB_ENC_HDRCRCSEED             0x11ac2097

#if _MSC_VER
#  pragma pack(push)
#  pragma pack(1)
#endif // #if _MSC_VER

typedef struct _gdsb_enc_hdr
{
    int signature;                          // encrypt header signature
    int hdr_cksum;                          // cksum of this header(excepted this member)
    int keys[GDSB_ENC_MAXKEYCOUNT];         // encrypt keys
    long long encrypted_size;               // size of encrypted file
    long long original_size;                // size of original file
    char reserved[424];                     // reserved(extend this struct to 512 bytes)
}
#ifdef __GNUC__
__attribute__((packed))
#endif // #ifdef __GNUC__
gdsb_enc_hdr;

#if _MSC_VER
#  pragma pack(pop)
#endif // #if _MSC_VER

static unsigned int gs_default_key[4] = {0xaa677410, 0x188de366, 0xf910885c, 0xb76003aa};

static void _get_really_key(int* keys, int key_cout, int realkey[4])
{
    int i = 0;
    memcpy(realkey, gs_default_key, sizeof(int) * 4);
    for (i = 0; i < key_cout; ++i)
    {
        realkey[i % 4] |= keys[i];
    }
}

static int _check_keys(int* keys, int key_count)
{
    int idx = 0;
    for (idx = 0; idx < key_count; ++idx)
    {
        if (!keys[idx])
        {
            return 1;
        }
    }

    return 0;
}

static int _gdsb_encrypt_to(hfile_t src, hfile_t dst, int* keys, int key_count)
{
    int ret = 1;

    do
    {
        int really_keys[4] = {0};
        gdsb_enc_hdr hdr = {0};
        char buf[4096];
        int bytes_readed;
        int io_flag = 0;

        if (0 != _check_keys(keys, key_count))
        {
            break;
        }

        hdr.original_size = hfile_getsize(src);
        if (!hdr.original_size)
        {
            break;
        }

        hdr.signature = GDSB_ENC_SIGNATURE;
        memcpy(hdr.keys, keys, sizeof(int) * key_count);

        _get_really_key(keys, key_count, really_keys);

        // move file pointer to data area(skip encrypt header)
        hfile_offset(dst, (hfile_off_t)sizeof(hdr), HOM_BEGIN);

        //(bytes_readed = hfile_read(src, buf, 4096))
        while ((bytes_readed = hfile_read(src, buf, 4096)))
        {
            char enced_buf[4096] = {0};
            int enced_size = 4096;
            htea_enc_buff_naked(buf, bytes_readed, (void *)really_keys, enced_buf, &enced_size);
            if (enced_size != hfile_write(dst, enced_buf, enced_size))
            {
                io_flag = 1;
                break;
            }
        }

        if (io_flag)
        {
            break;
        }

        hdr.encrypted_size = hfile_getsize(dst);
        hdr.hdr_cksum = hcrc32((const char*)&hdr, sizeof(hdr), GDSB_ENC_HDRCRCSEED);

        hfile_offset(dst, 0, HOM_BEGIN);
        if (sizeof(hdr) == hfile_write(dst, (const char*)&hdr, sizeof(hdr)))
        {
            ret = 0;
        }
    } while (0);

    return ret;
}

static int _check_enced_file(hfile_t f, int* keys, int* counts)
{
    int ret = 1;
    hfile_off_t off = hfile_offset(f, 0, HOM_BEGIN);

    do
    {
        gdsb_enc_hdr hdr = {0};
        int hdr_cksum = 0;

        if (sizeof(hdr) != hfile_read(f, (char*)&hdr, sizeof(hdr)))
        {
            break;
        }

        if (GDSB_ENC_SIGNATURE != hdr.signature)
        {
            break;
        }

        if (hdr.encrypted_size != hfile_getsize(f))
        {
            break;
        }

        hdr_cksum = hdr.hdr_cksum;
        hdr.hdr_cksum = 0;
        if (hcrc32((const char*)&hdr, sizeof(hdr), GDSB_ENC_HDRCRCSEED) != hdr_cksum)
        {
            break;
        }

        if (keys && counts)
        {
            int idx = 0;
            int size = *counts;
            *counts = 0;

            for (idx = 0; idx < size && idx < GDSB_ENC_MAXKEYCOUNT; ++idx)
            {
                if (!hdr.keys[idx])
                {
                    break;
                }

                keys[idx] = hdr.keys[idx];
                (*counts)++;
            }

        }

        ret = 0;
    } while (0);

    hfile_offset(f, off, HOM_BEGIN);

    return ret;
}

static int _check_intersection(int* keys1, int count1, int* keys2, int count2)
{
    int i = 0;
    int j = 0;

    for (i = 0; i < count1; ++i)
    {
        for (j = 0; j < count2; ++j)
        {
            if (keys1[i] == keys2[j])
            {
                return 0;
            }
        }
    }

    return 1;
}

static int _gdsb_decrypt_to(hfile_t src, hfile_t dst, int* keys, int key_count)
{
    int ret = 1;

    do
    {
        int file_keys[GDSB_ENC_MAXKEYCOUNT] = {0};
        int file_key_count = GDSB_ENC_MAXKEYCOUNT;
        int really_keys[4] = {0};
        gdsb_enc_hdr hdr = {0};
        char buf[4096];
        int bytes_readed;
        int io_flag = 0;

        if (_check_enced_file(src, file_keys, &file_key_count))
        {
            break;
        }

        if (file_key_count && _check_intersection(keys, key_count, file_keys, file_key_count))
        {
            break;
        }

        if (sizeof(hdr) != hfile_read(src, (char*)&hdr, sizeof(hdr)))
        {
            break;
        }

        _get_really_key(file_keys, file_key_count, really_keys);

        //bytes_readed = hfile_read(src, buf, 4096)
        while ((bytes_readed = hfile_read(src, buf, 4096)))
        {
            char deced_buf[4096] = {0};
            int deced_size = 4096;
            htea_dec_buff_naked(buf, bytes_readed, (void *)really_keys, deced_buf, &deced_size);
            if (deced_size != hfile_write(dst, deced_buf, deced_size))
            {
                io_flag = 1;
                break;
            }
        }

        if (io_flag)
        {
            break;
        }

        hfile_offset(dst, hdr.original_size, HOM_BEGIN);
        ret = hfile_truncate(dst);
    } while (0);

    return ret;
}

int HPLATFORMCALL gdsb_encrypt_to(const char* enc_this, const char* to_this, int* keys, int key_count)
{
    int ret = 1;
    hfile_t src_file = HF_INVALID_FILE;
    hfile_t dst_file = HF_INVALID_FILE;

    do
    {
        if (!enc_this || !to_this || key_count > GDSB_ENC_MAXKEYCOUNT)
        {
            break;
        }

        src_file = hfile_open(enc_this, HOA_READ, HOF_OPEN);
        if (HF_INVALID_FILE == src_file)
        {
            break;
        }

        dst_file = hfile_open(to_this, HOA_WRITE, HOF_CREATE);
        if (HF_INVALID_FILE == dst_file)
        {
            break;
        }

        ret = _gdsb_encrypt_to(src_file, dst_file, keys, key_count);
    } while (0);

    if (HF_INVALID_FILE != src_file)
    {
        hfile_close(src_file);
    }

    if (HF_INVALID_FILE != dst_file)
    {
        hfile_close(dst_file);
    }

    if (0 != ret)
    {
        hfile_delete(to_this);
    }

    return ret;
}

#if _MSC_VER
int HPLATFORMCALL gdsb_encrypt_to16(const wchar_t* enc_this, const wchar_t* to_this, int* keys, int key_count)
{
    int ret = 1;
    hfile_t src_file = HF_INVALID_FILE;
    hfile_t dst_file = HF_INVALID_FILE;

    do
    {
        if (!enc_this || !to_this || key_count > GDSB_ENC_MAXKEYCOUNT)
        {
            break;
        }

        src_file = hfile_open16(enc_this, HOA_READ, HOF_OPEN);
        if (HF_INVALID_FILE == src_file)
        {
            break;
        }

        dst_file = hfile_open16(to_this, HOA_WRITE, HOF_CREATE);
        if (HF_INVALID_FILE == dst_file)
        {
            break;
        }

        ret = _gdsb_encrypt_to(src_file, dst_file, keys, key_count);
    } while (0);

    if (HF_INVALID_FILE != src_file)
    {
        hfile_close(src_file);
    }

    if (HF_INVALID_FILE != dst_file)
    {
        hfile_close(dst_file);
    }

    if (0 != ret)
    {
        hfile_delete16(to_this);
    }

    return ret;
}
#endif // #if _MSC_VER

int HPLATFORMCALL gdsb_decrypt_to(const char* dec_this, const char* to_this, int* keys, int key_count)
{
    int ret = 1;
    hfile_t src_file = HF_INVALID_FILE;
    hfile_t dst_file = HF_INVALID_FILE;

    do
    {
        if (!dec_this || !to_this || key_count > GDSB_ENC_MAXKEYCOUNT)
        {
            break;
        }

        src_file = hfile_open(dec_this, HOA_READ, HOF_OPEN);
        if (HF_INVALID_FILE == src_file)
        {
            break;
        }

        dst_file = hfile_open(to_this, HOA_WRITE, HOF_CREATE);
        if (HF_INVALID_FILE == dst_file)
        {
            break;
        }

        ret = _gdsb_decrypt_to(src_file, dst_file, keys, key_count);
    } while (0);

    if (HF_INVALID_FILE != src_file)
    {
        hfile_close(src_file);
    }

    if (HF_INVALID_FILE != dst_file)
    {
        hfile_close(dst_file);
    }

    if (0 != ret)
    {
        hfile_delete(to_this);
    }

    return ret;
}

#if _MSC_VER
int HPLATFORMCALL gdsb_decrypt_to16(const wchar_t* dec_this, const wchar_t* to_this, int* keys, int key_count)
{
    int ret = 1;
    hfile_t src_file = HF_INVALID_FILE;
    hfile_t dst_file = HF_INVALID_FILE;

    do
    {
        if (!dec_this || !to_this || key_count > GDSB_ENC_MAXKEYCOUNT)
        {
            break;
        }

        src_file = hfile_open16(dec_this, HOA_READ, HOF_OPEN);
        if (HF_INVALID_FILE == src_file)
        {
            break;
        }

        dst_file = hfile_open16(to_this, HOA_WRITE, HOF_CREATE);
        if (HF_INVALID_FILE == dst_file)
        {
            break;
        }

        ret = _gdsb_decrypt_to(src_file, dst_file, keys, key_count);
    } while (0);

    if (HF_INVALID_FILE != src_file)
    {
        hfile_close(src_file);
    }

    if (HF_INVALID_FILE != dst_file)
    {
        hfile_close(dst_file);
    }

    if (0 != ret)
    {
        hfile_delete16(to_this);
    }

    return ret;
}
#endif // #if _MSC_VER

int HPLATFORMCALL gdsb_getenckeys(const char* file_name, int* keys, int* key_count)
{
    hfile_t f = hfile_open(file_name, HOA_READ, HOF_OPEN);
    if (HF_INVALID_FILE != f)
    {
        int ret = _check_enced_file(f, keys, key_count);
        hfile_close(f);
        return ret;
    }

    return 1;
}

#if _MSC_VER
int HPLATFORMCALL gdsb_getenckeys16(const wchar_t* file_name, int* keys, int* key_count)
{
    hfile_t f = hfile_open16(file_name, HOA_READ, HOF_OPEN);
    if (HF_INVALID_FILE != f)
    {
        int ret = _check_enced_file(f, keys, key_count);
        hfile_close(f);
        return ret;
    }

    return 1;
}
#endif // #if _MSC_VER
